package com.naiterui.ehp.bs.weixin.wechat;

import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

import org.apache.commons.lang3.StringUtils;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;

import com.naiterui.common.redis.RedisUtil;
import com.naiterui.ehp.bp.support.utils.FileUtil;
import com.naiterui.ehp.bp.support.utils.JsonMapper;
import com.naiterui.ehp.bp.utils.lang.ConfigUtil;
import com.naiterui.ehp.bp.utils.net.RestHttpUtil;
import com.naiterui.ehp.bs.weixin.subscribe.SubscribeAdd;
import com.naiterui.ehp.bs.weixin.subscribe.SubscribeAddResult;
import com.naiterui.ehp.bs.weixin.subscribe.SubscribeList;
import com.naiterui.ehp.bs.weixin.subscribe.SubscribeMsg;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;

/**
 * 微信接口封装工具
 *
 * @author zhoupeipei
 * @date 2019-06-12 13:54
 * @since 1.0.0
 */
@Slf4j
public class WeChatApiUtil {

    /**
     * client_credential
     */
    private static final String GRANT_TYPE_CLIENT_CREDENTIAL = "client_credential";
    public static final String WECHAT_BASE_URL = "https://api.weixin.qq.com";
    /**
     * 微信获取accessToken接口地址
     */
    private static final String WECHAT_URL_FOR_ACCESS_TOKEN = WECHAT_BASE_URL + "/cgi-bin/token?";
    /**
     * 订阅通知url
     */
    public static final String WECHAT_SUBSCRIBE_MESSAGE_BASE_URL = WECHAT_BASE_URL + "/wxaapi/newtmpl/";
    /**
     * 选用模板
     */
    public static final String WECHAT_SUBSCRIBE_MESSAGE_ADD = WECHAT_SUBSCRIBE_MESSAGE_BASE_URL + "addtemplate?access_token=";
    /**
     * 获取私有模板列表
     */
    public static final String WECHAT_SUBSCRIBE_MESSAGE_GETTEMPLATE = WECHAT_SUBSCRIBE_MESSAGE_BASE_URL + "gettemplate?access_token=";
    /**
     * 发送订阅通知
     */
    public static final String WECHAT_SUBSCRIBE_MESSAGE_SEND =
        WECHAT_BASE_URL + "/cgi-bin/message/subscribe/send?access_token=";
    /**
     * 生成小程序二维码
     */
    public static final String WECHAT_CREATE_QRCODE = WECHAT_BASE_URL + "/cgi-bin/wxaapp/createwxaqrcode?access_token=";
    /**
     * accesstoken定时刷新回调地址
     */
    private static final String ACCESS_TOKEN_REFRESH_URL = ConfigUtil.getString("wechat.access.token.refresh");
    /**
     * accesstoken 自动刷新周期  110分钟，提前4分钟刷新
     */
    private static final int ACCESS_TOKEN_REFRESH_UNIT = 111;
    private static final int WECHAT_API_RETRY_MAX = 2;

    /**
     * 从微信获取AccessToken，并放入缓存，更新刷新任务，正常情况下，1小时50分钟执行一次，为避免并发调度导致AccessToken混乱，加锁
     *
     * @param appId  小程序appid
     * @param secret 小程序secret
     * @author : zhoupeipei
     * @Date： 2019/1/30
     */
//    public static String refreshAccessToken(String appId, String secret) {
//        Map<String, String> paramMap = new HashMap<>(5);
//        paramMap.put("appid", appId);
//        paramMap.put("secret", secret);
//        paramMap.put("grant_type", GRANT_TYPE_CLIENT_CREDENTIAL);
//        ResponseEntity<AccessToken> responseEntity =
//                RestHttpUtil.get().url(WECHAT_URL_FOR_ACCESS_TOKEN).paramMap(paramMap).execute(new ParameterizedTypeReference<AccessToken>() {
//                });
//        AccessToken accessTokenResult = responseEntity.getBody();
//        if (ObjectUtil.isNull(accessTokenResult)) {
//            log.error("获取AccessToken失败，结果：{}", accessTokenResult);
//            return null;
//        }
//        String accessToken = accessTokenResult.getAccessToken();
//        if (StringUtils.isBlank(accessToken)) {
//            log.error("获取AccessToken失败，结果：{}", accessTokenResult);
//            return accessToken;
//        }
//        //新token放入缓存
//        //微信AccessToken有效期2小时,缓存1小时55分，提前失效，微信会保证新旧token5分钟内可共同使用
//        RedisUtil.valueOps().setString(WeChatCache.accessTokenKey(appId), accessToken, WeChatCache.ACCESS_TOKEN_CACHE_TIME);
//
//        // 添加定时任务，1小时51分后刷新缓存，先调任务删除接口，再添加新的调度
//        Date refreshTime = DateUtil.addMinutes(new Date(), ACCESS_TOKEN_REFRESH_UNIT);
//        String refreshTimeStr = DateUtil.formatDate(refreshTime, DateUtil.CRON_TIME_FORMAT);
//        Map<String, String> scheduleParam = new HashMap<>(5);
//        try {
//            //先删除未执行的刷新任务
//            ScheduleUtils.removeCustomerTask(ConfigUtil.getString(WeixinMaConstant.TASK_SCHEDULE_HOST), ScheduleUtils.TASK_TYPE_ACCESS_TOKEN, appId);
//            //添加新的刷新任务
//            ScheduleUtils.addCustomerTask(ConfigUtil.getString(WeixinMaConstant.TASK_SCHEDULE_HOST), refreshTimeStr,
//                    ScheduleUtils.TASK_TYPE_ACCESS_TOKEN, appId, ACCESS_TOKEN_REFRESH_URL, scheduleParam);
//        } catch (Exception e) {
//            log.error("accessToken刷新任务添加失败", e);
//        }
//        return accessToken;
//    }

    /**
     * 从微信获取AccessToken，并放入缓存，更新刷新任务，正常情况下，1小时50分钟执行一次，为避免并发调度导致AccessToken混乱，加锁
     *
     * @param appId  小程序appid
     * @param secret 小程序secret
     * @author : zhoupeipei
     * @Date： 2019/1/30
     */
    public static String getAccessToken(String appId, String secret) {
        Map<String, String> paramMap = new HashMap<>(5);
        paramMap.put("appid", appId);
        paramMap.put("secret", secret);
        paramMap.put("grant_type", GRANT_TYPE_CLIENT_CREDENTIAL);
        ResponseEntity<AccessToken> responseEntity =
                RestHttpUtil.get().url(WECHAT_URL_FOR_ACCESS_TOKEN).paramMap(paramMap).execute(new ParameterizedTypeReference<AccessToken>() {
                });
        AccessToken accessTokenResult = responseEntity.getBody();
        log.info("获取AccessToken, 结果：{}", accessTokenResult);
        if (ObjectUtil.isNull(accessTokenResult)) {
            log.error("获取AccessToken失败，结果：{}", accessTokenResult);
            return null;
        }
        String accessToken = accessTokenResult.getAccessToken();
        if (StringUtils.isBlank(accessToken)) {
            log.error("获取AccessToken失败，结果：{}", accessTokenResult);
            return accessToken;
        }
        Map<String, String> hash = new HashMap<>();
        hash.put("value", accessToken);
        hash.put("expire", String.valueOf(System.currentTimeMillis() + (accessTokenResult.getExpires() - 200) * 1000L));
        RedisUtil.hashOps().hmset(WeChatCache.ACCESS_TOEKN, hash);
        return accessToken;
    }

    /**
     * 获取已配置消息模板
     *
     * @param accessToken 微信accesstoken
     * @return 模板
     */
    public static SubscribeList getSubscribeList(String accessToken) {
        String url = WECHAT_SUBSCRIBE_MESSAGE_GETTEMPLATE + accessToken;
        return RestHttpUtil.get()
                .url(url)
                .execute(new ParameterizedTypeReference<SubscribeList>() {
                }).getBody();
    }

    /**
     * 选用模板
     *
     * @param accessToken /
     * @return /
     */
    public static SubscribeAddResult setSubscribeTemplate(String accessToken, SubscribeAdd subscribeAdd) {
        String url = WECHAT_SUBSCRIBE_MESSAGE_ADD + accessToken;
        log.info("wechat subscribe message add url: {}, body: {}", url, JSONUtil.toJsonStr(subscribeAdd));
        String result = HttpUtil.post(url, JSONUtil.toJsonStr(subscribeAdd));
        return JSONUtil.toBean(result, SubscribeAddResult.class);
    }

    /**
     * 发送订阅消息
     *
     * @param accessToken accessToken
     * @param msg         消息内容
     * @return 状态码 0.成功
     * 40003.touser字段openid为空或者不正确
     * 40037.订阅模板id为空不正确
     * 43101.用户拒绝接受消息，如果用户之前曾经订阅过，则表示用户取消了订阅关系
     * 47003.模板参数不准确，可能为空或者不满足规则，errmsg会提示具体是哪个字段出错
     * 41030.page路径不正确
     */
    public static Integer sendSubscribeMsg(String accessToken, SubscribeMsg msg) {
        String url = WECHAT_SUBSCRIBE_MESSAGE_SEND + accessToken;
        String result = postWeChat(url, msg);
        log.info("推送订阅通知消息结果，{}", result);
        JSONObject jsonObject = JSONUtil.parseObj(result);
        Integer errcode = jsonObject.getInt("errcode");
        if (errcode != 0) {
            log.error("推送订阅通知消息失败，{}", JSONUtil.toJsonStr(msg));
        }
        return errcode;
    }

    /**
     * 生成小程序二维码
     * 
     * @param accessToken
     * @param path
     *            扫码进入的小程序页面路径，最大长度 128 字节，不能为空；对于小游戏，可以只传入 query 部分，来实现传参效果，如：传入 "?foo=bar"，即可在 wx.getLaunchOptionsSync
     *            接口中的 query 参数获取到 {foo:"bar"}。
     * @param width
     *            二维码的宽度，单位 px。最小 280px，最大 1280px；默认430
     * @return
     */
    public static String createQrCode(String accessToken, String path, Integer width) throws Exception {
        String url = WECHAT_CREATE_QRCODE + accessToken;
        JSONObject obj = JSONUtil.createObj().set("path", path);
        if (width != null) {
            obj.set("width", width);
        }
        HttpResponse execute = HttpUtil.createPost(url).body(obj.toString()).execute();
        if (JSONUtil.isJson(execute.body())) {
            log.warn("小程序二维码生成失败 msg:{}", execute.body());
            return "";
        }
        String filePath = ConfigUtil.getString("file.base.prefix") + ConfigUtil.getString("ehp.miniapp-qrcode");
        String fileName = UUID.randomUUID() + ".png";
        FileUtil.inputStreamToFile(execute.bodyStream(), filePath, fileName);
        return ConfigUtil.getString("ehp.miniapp-qrcode") + fileName;
    }

    /**
     * post请求微信，发送客服消息、模板消息
     *
     * @param url   请求地址
     * @param msger 消息内容
     * @return /
     */
    private static String postWeChat(String url, SubscribeMsg msger) {
        String body;
        try {
            body = JsonMapper.toJson(msger);
        } catch (Exception e) {
            log.error("Json转换异常", e);
            return "";
        }

        int count = 1;
        ResponseEntity<String> responseEntity;
        do {
            responseEntity = RestHttpUtil.post()
                    .url(url)
                    .body(body)
                    .execute(new ParameterizedTypeReference<String>() {
                    });
        } while (HttpStatus.REQUEST_TIMEOUT.equals(responseEntity.getStatusCode()) && count++ <= WECHAT_API_RETRY_MAX);

        return responseEntity.getBody();
    }
}
